package org.qiyu.live.user.provider.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.producer.MQProducer;
import org.apache.rocketmq.common.message.Message;
import org.qiyu.live.common.interfaces.utils.ConvertBeanUtils;
import org.qiyu.live.framework.redis.starter.key.UserProviderCacheKeyBuilder;
import org.qiyu.live.user.constants.CacheAsyncDeleteCode;
import org.qiyu.live.user.constants.UserTagsEnum;
import org.qiyu.live.user.constants.UserTagsFilenamesConstants;
import org.qiyu.live.user.dto.UserCacheAsyncDeleteDTO;
import org.qiyu.live.user.dto.UserDTO;
import org.qiyu.live.user.dto.UserTagDTO;
import org.qiyu.live.user.provider.dao.mapper.IUserMapper;
import org.qiyu.live.user.provider.dao.mapper.IUserTagMapper;
import org.qiyu.live.user.provider.dao.po.UserPO;
import org.qiyu.live.user.provider.dao.po.UserTagPO;
import org.qiyu.live.user.provider.service.IUserService;
import org.qiyu.live.user.provider.service.IUserTagService;
import org.qiyu.live.user.utils.TagInfoUtils;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Service;

import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static org.qiyu.live.user.constants.UserProviderTopicNames.CACHE_ASYNC_DELETE_TOPIC;

@Service
@Slf4j
public class UserTagServiceImpl implements IUserTagService {

    @Resource
    private IUserTagMapper userTagMapper;

    @Resource
    private MQProducer mqProducer;


    @Resource
    private RedisTemplate<String, UserTagDTO> redisTemplate;

    @Resource
    private UserProviderCacheKeyBuilder cacheKeyBuilder;


    @Override

    public boolean setTag(Long userId, UserTagsEnum userTagsEnum) {

        //尝试update true; return
        //尝试update false;  有两种情况，1 数据库查不到这个人信息，2 数据库查到了，但是已经设置了标签
        boolean updateStatus = userTagMapper.setTag(userId, userTagsEnum.getFieldName(), userTagsEnum.getTag()) > 0;
        if (updateStatus) {
            deleteUserTagDTOFromRedis(userId);
            return true;
        }

        //方案1
//        redisTemplate.opsForValue().setIfAbsent("myKey", "myValue", 3, TimeUnit.SECONDS);

        //方案2
//        redisTemplate.opsForValue().setIfAbsent("myKey", "myValue");
//        redisTemplate.expire("myKey", 3, TimeUnit.SECONDS);  //但是这种写法不具备原子性操作，在高并发场景下存在风险

        String setNxKey = cacheKeyBuilder.buildTagLockKey(userId);
        //这段Java代码是通过RedisTemplate执行一个自定义的Redis命令。它与redisTemplate.opsForValue().setIfAbsent()方法的主要区别在于：
        //直接使用Redis原生命令“SET”：该代码片段使用Redis的SET命令，并且指定了NX（如果键不存在）和EX（设置过期时间）选项，这样可以实现类似于setIfAbsent的功能，同时设置键值对并指定其在30秒后过期。
        //序列化处理：这里的键和值都需要通过RedisSerializer进行序列化，以确保它们能在Redis中正确存储。而opsForValue().setIfAbsent()方法内部已经隐式处理了序列化问题。
        //手动控制命令参数：这种方法提供了更底层的操作方式，可以直接控制发送给Redis服务器的命令及其参数，具有更高的灵活性，但同时也需要开发者对Redis命令有深入理解。
        //异常处理：这个回调函数需要捕获并处理可能抛出的DataAccessException异常，而opsForValue().setIfAbsent()方法通常会包装并转换这些异常为Spring Data Redis的异常类型。
        //总结来说，这段代码实现了类似setIfAbsent的行为，但在处理序列化、命令构造和异常方面更为底层和灵活。

        //方案3
        String setNxResult = redisTemplate.execute(new RedisCallback<String>() {
            @Override
            public String doInRedis(RedisConnection connection) throws DataAccessException {
                RedisSerializer keySerializer = redisTemplate.getKeySerializer();
                RedisSerializer valueSerializer = redisTemplate.getValueSerializer();
                return (String) connection.execute("set", keySerializer.serialize(setNxKey),
                        valueSerializer.serialize("-1"),
                        "NX".getBytes(StandardCharsets.UTF_8),
                        "EX".getBytes(StandardCharsets.UTF_8),
                        "3".getBytes(StandardCharsets.UTF_8));
            }
        });

        if (!"OK".equals(setNxResult)) {
            return false;
        }



        UserTagPO userTagPO = userTagMapper.selectById(userId);
        if (userTagPO == null) {
            //查不到这个人的记录
            UserTagPO newInstance = new UserTagPO();
            newInstance.setUserId(userId);
            userTagMapper.insert(newInstance);

            //删除锁
            redisTemplate.delete(setNxKey);

            return userTagMapper.setTag(userId, userTagsEnum.getFieldName(), userTagsEnum.getTag()) > 0;

        } else {
            return false; // 查到了更新不了，return false
        }
    }

    @Override
    public boolean cancelTag(Long userId, UserTagsEnum userTagsEnum) {
        boolean cancelStatus = userTagMapper.cancelTag(userId, userTagsEnum.getFieldName(), userTagsEnum.getTag()) > 0;
        if (!cancelStatus) {
            return false;
        }
        deleteUserTagDTOFromRedis(userId);
        return true;
    }

    @Override
    public boolean containTag(Long userId, UserTagsEnum userTagsEnum) {
        boolean updateStatus = userTagMapper.containTag(userId, userTagsEnum.getFieldName(), userTagsEnum.getTag()) > 0;
        return updateStatus;
    }

    @Override
    public boolean containTagPlanB(Long userId, UserTagsEnum userTagsEnum) {

        UserTagDTO userTagDTO =queryByUserIdFromRedis(userId);
//        UserTagPO userTagPO = userTagMapper.selectById(userId);
        if (userTagDTO == null) {
            return false;
        }
        //下面要进行字符串的匹配，这边使用常量类 UserTagFieldNameConstants  这个常量类放在 qiyu-live-user-interface 里面
        if (UserTagsFilenamesConstants.TAG_INFO_01.equals(userTagsEnum.getFieldName())) {
            return TagInfoUtils.isContain(userTagDTO.getTagInfo01(), userTagsEnum.getTag());
        }


        return false;
    }

    /**
     * 从redis中删除用户标签对象
     * @param userId
     * @return
     */
    private void deleteUserTagDTOFromRedis(Long userId) {
        String redisKey = cacheKeyBuilder.buildTagKey(userId);
        redisTemplate.delete(redisKey);


        UserCacheAsyncDeleteDTO userCacheAsyncDeleteDTO = new UserCacheAsyncDeleteDTO();
        userCacheAsyncDeleteDTO.setCode(CacheAsyncDeleteCode.USER_TAG_DELETE.getCode());

        HashMap<String, Long> params = new HashMap<>();
        params.put("userId", userId);
        userCacheAsyncDeleteDTO.setJson(JSON.toJSONString(params));

        Message message = new Message();
        message.setBody(JSON.toJSONString(userCacheAsyncDeleteDTO).getBytes());
        message.setTopic(CACHE_ASYNC_DELETE_TOPIC);
        message.setDelayTimeLevel(1);

        try {
            mqProducer.send(message);
        } catch (Exception e) {
            log.error("消息发送失败", e);
        }


    }


        /**
         * 从redis中查询用户标签对象
         * @param userId
         * @return
         */
    private UserTagDTO queryByUserIdFromRedis(Long userId) {
        String userTagKey = cacheKeyBuilder.buildTagKey(userId);
        //step1 尝试从redis查询
        UserTagDTO userTagDTO = redisTemplate.opsForValue().get(userTagKey);
        if (userTagDTO != null) {
            return userTagDTO;
        }
        //step2 redis 查不到，从数据库查询
        UserTagPO userTagPO = userTagMapper.selectById(userId);
        if (userTagPO == null) {
            return null;
        }else{
            //如果MySQL中查到了
            UserTagDTO convert = ConvertBeanUtils.convert(userTagPO, UserTagDTO.class);
            redisTemplate.opsForValue().setIfAbsent(userTagKey, convert, 30, TimeUnit.MINUTES);
            return convert;
        }
    }








}
